#include "coin2/exchange/deribit_base/api_order/fix/fix_message_builder2.h"

#include <utility>

#include <glog/logging.h>

#include "coin2/base/base64.h"
#include "coin2/exchange/base/api_base/asio_http_context.h"
#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/base/symbology/product.h"
#include "coin2/exchange/base/symbology/product_info.h"
#include "coin2/exchange/deribit_base/api_order/fix/fix_tags2.h"
#include "coin2/exchange/deribit_futures/symbology/product.h"
#include "coin2/exchange/deribit_options/symbology/product.h"

namespace coin2::exchange::deribit::api_order::fix {

using namespace ::naviasraw::fix;

DeribitOrder::DeribitOrder(
    const OrderExecution& ord_exec,
    const MarketExchangeApi& mea,
    GetProductHolderFunc get_product_holder,
    double contract_multiplier) {
  Parse(ord_exec, mea, get_product_holder, contract_multiplier);
  Validate();
}

void DeribitOrder::Parse(
    const OrderExecution& ord_exec,
    const MarketExchangeApi& mea,
    GetProductHolderFunc get_product_holder,
    double contract_multiplier) {
  using OrderType = coin2::exchange::base::order::OrderType;
  client_id = std::to_string(ord_exec.proc_order_id);
  symbol = ord_exec.order_spec->product;

  if (mea.market == coin::proto::Futures) {
    using coin2::exchange::deribit_futures::symbology::DeribitFuturesProduct;
    auto product = DeribitFuturesProduct::FromStr(
        ord_exec.order_spec->product,
        ord_exec.order_spec->order_created_time);
    auto product_holder = get_product_holder(product, ord_exec.order_spec->order_created_time);
    auto formatter = product_holder->product_info().order_formatter();
    // convert from USD value to num of contracts.
    quantity = stod(formatter.FormatQty(ord_exec.order_spec->qty / contract_multiplier));
    price = stod(formatter.FormatPrice(ord_exec.order_spec->price));
  } else if (mea.market == coin::proto::Options) {
    using coin2::exchange::deribit_options::symbology::DeribitOptionsProduct;
    auto product = DeribitOptionsProduct::FromStr(
        ord_exec.order_spec->product,
        ord_exec.order_spec->order_created_time);
    auto product_holder = get_product_holder(product, ord_exec.order_spec->order_created_time);
    auto formatter = product_holder->product_info().order_formatter();
    // convert from USD value to num of contracts.
    quantity = stod(formatter.FormatQty(ord_exec.order_spec->qty / contract_multiplier));
    price = stod(formatter.FormatPrice(ord_exec.order_spec->price));
  } else {
    CHECK(false) << "Unknown market type " << mea.String();
  }

  type = ord_exec.order_spec->order_type == OrderType::MARKET_ORDER ? MARKET : LIMIT;
  side = ord_exec.order_spec->order_side == 1 ? BUY : SELL;

  const auto& duration = ord_exec.order_spec->duration;
  if (duration == coin::proto::OrderDuration::FOK_ORDER) {
    time_in_force = FILL_OR_KILL;
  } else if (duration == coin::proto::OrderDuration::IOC_ORDER) {
    time_in_force = IMMEDIATE_OR_CANCEL;
  } else if (duration == coin::proto::OrderDuration::GTC_ORDER) {
    time_in_force = GOOD_TILL_CANCEL;
  } else {
    LOG(INFO) << "Unknown OrderDuration." << static_cast<int>(duration);
    time_in_force = GOOD_TILL_CANCEL;
  }

  if (ord_exec.order_spec->post_only) {
    exec_inst = "6A";  // REJECT POST_ONLY
  }
}

void DeribitOrder::Validate() const {
  // TODO(xguo) - validate fields
}

FixMessageBuilder2::FixMessageBuilder2(
    const MarketExchangeApi& mea,
    const std::string& begin_string,
    const std::string& sender_comp_id,
    const std::string& target_comp_id)
    : mea_{mea},
      begin_string_(begin_string),
      sender_comp_id_(sender_comp_id),
      target_comp_id_(target_comp_id),
      logon_(begin_string_, "A", sender_comp_id_, target_comp_id_, ""),
      logout_(begin_string_, "5", sender_comp_id_, target_comp_id_, ""),
      limit_order_(begin_string_, "D", sender_comp_id_, target_comp_id_, "40=2"),
      cancel_(begin_string_, "F", sender_comp_id_, target_comp_id_, ""),
      security_list_req_(begin_string_, "x", sender_comp_id_, target_comp_id_, "") {}

FixMessageBuilder2::Request* FixMessageBuilder2::GenerateLogon(
    char* buf,
    size_t size,
    const std::string& user_name,
    const std::string& secret_key,
    const std::string& cancel_orders_on_disconnect) {
  if (__glibc_unlikely(buf == nullptr)) {
    LOG(WARNING) << "Empty buf, generate message failed";
    return nullptr;
  }
  logon_.Bind(buf, size);

  int64_t curr_ts = GetCurrentTimestamp();
  std::string nonce =
      impl::base64_encode(reinterpret_cast<const unsigned char*>(&curr_ts), sizeof(curr_ts));
  std::string raw_data = std::to_string(curr_ts) + "." + nonce;
  std::string password = ApiSignatureUtil::GetHashBySHA256(raw_data + secret_key);

  if (__glibc_unlikely(
          logon_.AddTag(tag::common::HeartBtInt(), 60u) <= 0 ||
          logon_.AddTag(tag::common::RawData(), raw_data) <= 0 ||
          logon_.AddTag(tag::common::Username(), user_name) <= 0 ||
          logon_.AddTag(tag::common::Password(), password) <= 0))
    return nullptr;
  DCHECK_EQ(cancel_orders_on_disconnect.size(), 1);
  if (__glibc_unlikely(
          logon_.AddTag(tag::deribit::CancelOrdersOnDisconnect(), cancel_orders_on_disconnect) <=
          0))
    return nullptr;
  return &logon_;
}

FixMessageBuilder2::Request* FixMessageBuilder2::GenerateLogout(char* buf, size_t size) {
  if (__glibc_unlikely(buf == nullptr)) {
    LOG(WARNING) << "Empty buf, generate message failed";
    return nullptr;
  }
  logout_.Bind(buf, size);
  return &logout_;
}

FixMessageBuilder2::Request* FixMessageBuilder2::GeneratePlaceOrder(
    char* buf,
    size_t size,
    const OrderExecution& ord_exec,
    GetProductHolderFunc get_product_holder,
    double contract_multiplier) {
  if (__glibc_unlikely(buf == nullptr)) {
    LOG(WARNING) << "Empty buf, generate message failed";
    return nullptr;
  }
  limit_order_.Bind(buf, size);
  DeribitOrder order{ord_exec, mea_, get_product_holder, contract_multiplier};
  if (__glibc_unlikely(
          limit_order_.AddTag(tag::common::ClOrdID(), order.client_id) <= 0 ||
          // Make sure rest/ws can see client order id.
          limit_order_.AddTag(tag::deribit::DeribitLabel(), order.client_id) <= 0 ||
          limit_order_.AddTag(tag::common::Side(), order.side) <= 0 ||
          limit_order_.AddTag(tag::common::OrderQty(), order.quantity) <= 0 ||
          limit_order_.AddTag(tag::common::Symbol(), order.symbol) <= 0))
    return nullptr;
  if (order.exec_inst.empty() == false) {
    if (__glibc_unlikely(limit_order_.AddTag(tag::common::ExecInst(), order.exec_inst) <= 0))
      return nullptr;
  }
  switch (order.type) {
    case LIMIT: {
      if (__glibc_unlikely(
              limit_order_.AddTag(tag::common::Price(), order.price) > 0 &&
              limit_order_.AddTag(tag::common::TimeInForce(), order.time_in_force) > 0))
        return &limit_order_;
    } break;
    case MARKET: {
      LOG(FATAL) << "market order not supported!";
    } break;
    case STOP_LIMIT: {
      LOG(FATAL) << "stop_limit order not supported!";
    } break;
    default: {
      LOG(FATAL) << "not supported!";
    } break;
  }
  return nullptr;
}

FixMessageBuilder2::Request*
FixMessageBuilder2::GenerateCancelOrder(char* buf, size_t size, const OrderExecution& ord_exec) {
  if (__glibc_unlikely(buf == nullptr)) {
    LOG(WARNING) << "Empty buf, generate message failed";
    return nullptr;
  }
  if (ord_exec.oc == nullptr) {
    LOG(ERROR) << "order context not found!";
    return nullptr;
  }

  cancel_.Bind(buf, size);
  if (__glibc_unlikely(
          cancel_.AddTag(
              tag::common::OrigClOrdID(),
              std::string_view(ord_exec.oc->exchange_order_id())
                  .substr(ord_exec.oc->order_spec().product.length())) <= 0 ||
          cancel_.AddTag(tag::common::ClOrdID(), static_cast<uint64_t>(ord_exec.proc_order_id)) <=
              0))
    return nullptr;
  return &cancel_;
}

FixMessageBuilder2::Request* FixMessageBuilder2::GenerateSecurityListRequest(
    char* buf,
    size_t size) {
  if (__glibc_unlikely(buf == nullptr)) {
    LOG(WARNING) << "Empty buf, generate message failed";
    return nullptr;
  }
  security_list_req_.Bind(buf, size);
  if (__glibc_unlikely(
          security_list_req_.AddTag(
              tag::common::SecurityReqID(),
              static_cast<uint64_t>(GetCurrentTimestamp())) <= 0 ||
          security_list_req_.AddTag(tag::common::SecurityListRequestType(), 0u) <= 0))
    return nullptr;
  return &security_list_req_;
}

}  // namespace coin2::exchange::deribit::api_order::fix
